/* 
  Copyright 2001-2007 Markus Hahn 
  All rights reserved. See documentation for license details.  
*/

namespace EMISecurity.SymmetricCryptography.CryptoManaged
{
	/// <summary>Blowfish CBC implementation.</summary>
	/// <remarks>Use this class to encrypt or decrypt byte arrays or a single blocks
	/// with Blowfish in CBC (Cipher Block Block Chaining) mode. This is the recommended
	/// way to use Blowfish.NET, unless certain requirements (e.g. moving block without
	/// decryption) exist.
	/// </remarks>
	public class BlowfishCBC : BlowfishECB
	{
		// we store the IV as two 32bit integers, to void packing and
		// unpacking inbetween the handling of data chunks
		private uint ivHi;
		private uint ivLo;

		/// <summary>The current initialization vector (IV), which measures one block.</summary>
		public byte[] IV
		{
			set { SetIV(value, 0); }

			get
			{
				byte[] result = new byte[BLOCK_SIZE];
				GetIV(result, 0);
				return result;
			}
		}

		/// <summary>Sets the initialization vector (IV) with an offset.</summary>
		/// <param name="buf">The buffer containing the new IV material.</param>
		/// <param name="ofs">Where the IV material starts.</param>
		public void SetIV(byte[] buf, int ofs)
		{
			ivHi = (((uint) buf[ofs]) << 24) |
			       (((uint) buf[ofs + 1]) << 16) |
			       (((uint) buf[ofs + 2]) << 8) |
			       buf[ofs + 3];

			ivLo = (((uint) buf[ofs + 4]) << 24) |
			       (((uint) buf[ofs + 5]) << 16) |
			       (((uint) buf[ofs + 6]) << 8) |
			       buf[ofs + 7];
		}

		/// <summary>Gets the current IV material (of the size of one block).</summary>
		/// <param name="buf">The buffer to copy the IV to.</param>
		/// <param name="ofs">Where to start copying.</param>
		public void GetIV(byte[] buf, int ofs)
		{
			uint ivhi = ivHi;
			uint ivlo = ivLo;

			buf[ofs++] = (byte) (ivhi >> 24);
			buf[ofs++] = (byte) (ivhi >> 16);
			buf[ofs++] = (byte) (ivhi >> 8);
			buf[ofs++] = (byte) ivhi;

			buf[ofs++] = (byte) (ivlo >> 24);
			buf[ofs++] = (byte) (ivlo >> 16);
			buf[ofs++] = (byte) (ivlo >> 8);
			buf[ofs] = (byte) ivlo;
		}

		/// <summary>Default constructor.</summary>
		/// <remarks>The IV needs to be assigned after the instance has been created!</remarks>
		/// <see cref="BlowfishECB.Initialize"/>
		public BlowfishCBC(byte[] key, int ofs, int len) : base(key, ofs, len)
		{
		}

		/// <summary>Zero key constructor.</summary>
		/// <remarks>After construction you need to initialize the instance and then apply the IV.</remarks>
		public BlowfishCBC() : base(null, 0, 0)
		{
		}

		/// <see cref="BlowfishECB.Invalidate"/>
		public new void Invalidate()
		{
			base.Invalidate();

			ivHi = ivLo = 0;
		}

		/// <see cref="BlowfishECB.EncryptBlock"/>
		public new void EncryptBlock(
			uint hi,
			uint lo,
			out uint outHi,
			out uint outLo)
		{
			byte[] _block = block;

			_block[0] = (byte) (hi >> 24);
			_block[1] = (byte) (hi >> 16);
			_block[2] = (byte) (hi >> 8);
			_block[3] = (byte) hi;
			_block[4] = (byte) (lo >> 24);
			_block[5] = (byte) (lo >> 16);
			_block[6] = (byte) (lo >> 8);
			_block[7] = (byte) lo;

			Encrypt(_block, 0, _block, 0, BLOCK_SIZE);

			outHi = (((uint) _block[0]) << 24) |
			        (((uint) _block[1]) << 16) |
			        (((uint) _block[2]) << 8) |
			        _block[3];

			outLo = (((uint) _block[4]) << 24) |
			        (((uint) _block[5]) << 16) |
			        (((uint) _block[6]) << 8) |
			        _block[7];
		}

		/// <see cref="BlowfishECB.DecryptBlock"/>
		public new void DecryptBlock(
			uint hi,
			uint lo,
			out uint outHi,
			out uint outLo)
		{
			byte[] _block = block;

			_block[0] = (byte) (hi >> 24);
			_block[1] = (byte) (hi >> 16);
			_block[2] = (byte) (hi >> 8);
			_block[3] = (byte) hi;
			_block[4] = (byte) (lo >> 24);
			_block[5] = (byte) (lo >> 16);
			_block[6] = (byte) (lo >> 8);
			_block[7] = (byte) lo;

			Decrypt(_block, 0, _block, 0, BLOCK_SIZE);

			outHi = (((uint) _block[0]) << 24) |
			        (((uint) _block[1]) << 16) |
			        (((uint) _block[2]) << 8) |
			        _block[3];

			outLo = (((uint) _block[4]) << 24) |
			        (((uint) _block[5]) << 16) |
			        (((uint) _block[6]) << 8) |
			        _block[7];
		}

		/// <see cref="BlowfishECB.Encrypt"/>
		public new int Encrypt(
			byte[] dataIn,
			int posIn,
			byte[] dataOut,
			int posOut,
			int count)
		{
			int end;

			uint[] _sbox1 = sbox1;
			uint[] _sbox2 = sbox2;
			uint[] _sbox3 = sbox3;
			uint[] _sbox4 = sbox4;

			uint[] _pbox = pbox;

			uint pbox00 = _pbox[0];
			uint pbox01 = _pbox[1];
			uint pbox02 = _pbox[2];
			uint pbox03 = _pbox[3];
			uint pbox04 = _pbox[4];
			uint pbox05 = _pbox[5];
			uint pbox06 = _pbox[6];
			uint pbox07 = _pbox[7];
			uint pbox08 = _pbox[8];
			uint pbox09 = _pbox[9];
			uint pbox10 = _pbox[10];
			uint pbox11 = _pbox[11];
			uint pbox12 = _pbox[12];
			uint pbox13 = _pbox[13];
			uint pbox14 = _pbox[14];
			uint pbox15 = _pbox[15];
			uint pbox16 = _pbox[16];
			uint pbox17 = _pbox[17];

			uint hi = ivHi;
			uint lo = ivLo;

			count &= ~(BLOCK_SIZE - 1);

			end = posIn + count;

			while (posIn < end)
			{
				hi ^= (((uint) dataIn[posIn]) << 24) |
				      (((uint) dataIn[posIn + 1]) << 16) |
				      (((uint) dataIn[posIn + 2]) << 8) |
				      dataIn[posIn + 3];

				lo ^= (((uint) dataIn[posIn + 4]) << 24) |
				      (((uint) dataIn[posIn + 5]) << 16) |
				      (((uint) dataIn[posIn + 6]) << 8) |
				      dataIn[posIn + 7];

				posIn += 8;

				hi ^= pbox00;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox01;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox02;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox03;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox04;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox05;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox06;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox07;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox08;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox09;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox10;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox11;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox12;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox13;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox14;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox15;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox16;

				uint swap = lo ^ pbox17;
				lo = hi;
				hi = swap;

				dataOut[posOut] = (byte) (hi >> 24);
				dataOut[posOut + 1] = (byte) (hi >> 16);
				dataOut[posOut + 2] = (byte) (hi >> 8);
				dataOut[posOut + 3] = (byte) hi;

				dataOut[posOut + 4] = (byte) (lo >> 24);
				dataOut[posOut + 5] = (byte) (lo >> 16);
				dataOut[posOut + 6] = (byte) (lo >> 8);
				dataOut[posOut + 7] = (byte) lo;

				posOut += 8;
			}

			ivHi = hi;
			ivLo = lo;

			return count;
		}

		/// <see cref="BlowfishECB.Decrypt"/>
		public new int Decrypt(
			byte[] dataIn,
			int posIn,
			byte[] dataOut,
			int posOut,
			int count)
		{
			int end;

			uint[] _sbox1 = sbox1;
			uint[] _sbox2 = sbox2;
			uint[] _sbox3 = sbox3;
			uint[] _sbox4 = sbox4;

			uint[] _pbox = pbox;

			uint pbox00 = _pbox[0];
			uint pbox01 = _pbox[1];
			uint pbox02 = _pbox[2];
			uint pbox03 = _pbox[3];
			uint pbox04 = _pbox[4];
			uint pbox05 = _pbox[5];
			uint pbox06 = _pbox[6];
			uint pbox07 = _pbox[7];
			uint pbox08 = _pbox[8];
			uint pbox09 = _pbox[9];
			uint pbox10 = _pbox[10];
			uint pbox11 = _pbox[11];
			uint pbox12 = _pbox[12];
			uint pbox13 = _pbox[13];
			uint pbox14 = _pbox[14];
			uint pbox15 = _pbox[15];
			uint pbox16 = _pbox[16];
			uint pbox17 = _pbox[17];

			uint _ivHi = ivHi;
			uint _ivLo = ivLo;

			count &= ~(BLOCK_SIZE - 1);

			end = posIn + count;

			while (posIn < end)
			{
				uint hiBak;
				uint hi = hiBak = (((uint) dataIn[posIn]) << 24) |
				                  (((uint) dataIn[posIn + 1]) << 16) |
				                  (((uint) dataIn[posIn + 2]) << 8) |
				                  dataIn[posIn + 3];

				uint loBak;
				uint lo = loBak = (((uint) dataIn[posIn + 4]) << 24) |
				                  (((uint) dataIn[posIn + 5]) << 16) |
				                  (((uint) dataIn[posIn + 6]) << 8) |
				                  dataIn[posIn + 7];
				posIn += 8;

				hi ^= pbox17;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox16;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox15;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox14;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox13;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox12;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox11;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox10;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox09;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox08;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox07;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox06;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox05;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox04;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox03;
				lo ^= (((_sbox1[(int) (hi >> 24)] + _sbox2[(int) ((hi >> 16) & 0x0ff)]) ^ _sbox3[(int) ((hi >> 8) & 0x0ff)]) +
				       _sbox4[(int) (hi & 0x0ff)]) ^ pbox02;
				hi ^= (((_sbox1[(int) (lo >> 24)] + _sbox2[(int) ((lo >> 16) & 0x0ff)]) ^ _sbox3[(int) ((lo >> 8) & 0x0ff)]) +
				       _sbox4[(int) (lo & 0x0ff)]) ^ pbox01;

				lo ^= _ivHi ^ pbox00;
				hi ^= _ivLo;

				dataOut[posOut] = (byte) (lo >> 24);
				dataOut[posOut + 1] = (byte) (lo >> 16);
				dataOut[posOut + 2] = (byte) (lo >> 8);
				dataOut[posOut + 3] = (byte) lo;

				dataOut[posOut + 4] = (byte) (hi >> 24);
				dataOut[posOut + 5] = (byte) (hi >> 16);
				dataOut[posOut + 6] = (byte) (hi >> 8);
				dataOut[posOut + 7] = (byte) hi;

				_ivHi = hiBak;
				_ivLo = loBak;

				posOut += 8;
			}

			ivHi = _ivHi;
			ivLo = _ivLo;

			return count;
		}

		/// <see cref="BlowfishECB.Clone"/>
		public new object Clone()
		{
			BlowfishCBC result;

			result = new BlowfishCBC();

			result.pbox = (uint[]) pbox.Clone();

			result.sbox1 = (uint[]) sbox1.Clone();
			result.sbox2 = (uint[]) sbox2.Clone();
			result.sbox3 = (uint[]) sbox3.Clone();
			result.sbox4 = (uint[]) sbox4.Clone();

			result.block = (byte[]) block.Clone();

			result.isWeakKey = isWeakKey;

			result.ivHi = ivHi;
			result.ivLo = ivLo;

			return result;
		}
	}
}